﻿using System;                       // For String
using System.Collections.Generic;
using System.Xml.Serialization;
using System.IO;   // For Dictionary

namespace Common
{
    using Common.Controls;

    public class ParameterChangeArgs : EventArgs
    {
        public String Key { get; private set; }
        public String Value { get; private set; }
        public String OldValue { get; set; }

        public ParameterChangeArgs(String key, String old_value, String new_value)
        {
            Key = key;
            Value = new_value;
            OldValue = old_value;
        }
    }
    public delegate void ParameterChangeHandler(object sender, ParameterChangeArgs args);

    public class Parameter
    {
        [XmlElement("ParameterValue")]
        public String RawValue { get; set; }
        public String DefaultValue { get; set; }

        public Parameter() : this(string.Empty) { }

        public Parameter(String value)
        {
            DefaultValue = value;
            RawValue = value;
        }

        public event ParameterChangeHandler ParameterBeforeChange;
        public event ParameterChangeHandler ParameterAfterChange;

        [XmlIgnore()]
        public virtual String Value
        {
            get { return RawValue; }
            set
            {
                if (!RawValue.Equals(value))
                {
                    ParameterChangeArgs args = new ParameterChangeArgs(null, RawValue, value);

                    if (null != ParameterBeforeChange)
                        ParameterBeforeChange(this, args);

                    RawValue = args.Value;

                    if (null != ParameterAfterChange)
                        ParameterAfterChange(this, args);
                }
            }
        }

        public override String ToString()
        {
            return RawValue.ToString();
        }
    }

    public class EncryptedParameter : Parameter
    {
        private Encryption encryption_ = null;

        public EncryptedParameter() : this(string.Empty) { }

        public EncryptedParameter(String value) : this(value, "default_password") { }

        public EncryptedParameter(String value, String password)
            : base()
        {
            encryption_ = new Encryption(password);
            DefaultValue = value;
            Value = value;
        }

        public override String Value
        {
            get
            {
                if (!String.IsNullOrEmpty(base.Value))
                    return encryption_.Decrypt(base.Value);

                return base.Value;
            }

            set
            {
                if (!String.IsNullOrEmpty(value))
                {
                    base.Value = encryption_.Encrypt(value);
                }
                else
                {
                    base.Value = value;
                }
            }
        }
    }

    public class SettingsManager : IXmlSerializable
    {
        #region Serialization
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            bool was_empty = reader.IsEmptyElement;
            reader.Read();

            if (was_empty)
                return;

            reader.ReadStartElement("Parameters");
            while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
            {
                reader.ReadStartElement("Parameter");

                reader.ReadStartElement("Key");
                String key = reader.ReadString();
                reader.ReadEndElement();

                reader.ReadStartElement("Value");
                if (!parameters_.ContainsKey(key))
                {
                    Add(key, string.Empty);
                }

                parameters_[key].RawValue = reader.ReadString();

                reader.ReadEndElement();

                reader.ReadEndElement();
                reader.MoveToContent();
            }
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            XmlSerializer keySerializer = new XmlSerializer(typeof(String));

            writer.WriteStartElement("Parameters");
            foreach (var key in parameters_.Keys)
            {
                if (parameters_[key].Value == parameters_[key].DefaultValue)
                    continue;

                writer.WriteStartElement("Parameter");

                writer.WriteStartElement("Key");
                writer.WriteValue(key);
                writer.WriteEndElement();

                writer.WriteStartElement("Value");

                if (parameters_[key] is EncryptedParameter)
                {
                    writer.WriteStartAttribute("Type");
                    writer.WriteValue("Encrypted");
                    writer.WriteEndAttribute();
                }

                writer.WriteValue(parameters_[key].RawValue);
                writer.WriteEndElement();

                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }
        #endregion Serialization

        private Dictionary<String, Parameter> parameters_ = new Dictionary<string, Parameter>();

        public SettingsManager() { }

        public SettingsManager(String parameters)
            : this()
        {
            foreach (String s in parameters.Split(new String[] { ";" }, StringSplitOptions.RemoveEmptyEntries))
            {
                String[] pieces = s.Split(new String[] { "=" }, StringSplitOptions.RemoveEmptyEntries);

                this.Add(pieces[0]);
            }

            Set(parameters);
        }

        [XmlIgnore()]
        public Dictionary<String, Parameter>.KeyCollection Keys
        {
            get { return parameters_.Keys; }
        }

        public void Set(String parameters)
        {
            Set(parameters, false);
        }

        public void Set(String parameters, bool add_missing)
        {
            if (!string.IsNullOrEmpty(parameters))
            {
                foreach (String s in parameters.Split(new String[] { ";" }, StringSplitOptions.RemoveEmptyEntries))
                {
                    String[] pieces = s.Split(new String[] { "=" }, StringSplitOptions.RemoveEmptyEntries);

                    if (add_missing && !parameters_.ContainsKey(pieces[0]))
                    {
                        Add(pieces[0]);
                    }

                    if (parameters_.ContainsKey(pieces[0]))
                    {
                        parameters_[pieces[0]].RawValue = pieces.Length > 1 ? pieces[1] : string.Empty;
                    }
                }
            }
        }

        public override string ToString()
        {
            String result = String.Empty;

            foreach (var parameter in parameters_)
            {
                if (parameter.Value.Value != parameter.Value.DefaultValue)
                    result += String.Format("{0}={1};", parameter.Key, parameter.Value.ToString());
            }

            return result;
        }

        public event ParameterChangeHandler ParameterBeforeChange;
        public event ParameterChangeHandler ParameterAfterChange;

        public virtual String this[String key]
        {
            get
            {
                if (parameters_.ContainsKey(key))
                    return parameters_[key].Value;

                return null;
            }

            set
            {
                if (parameters_.ContainsKey(key) && !parameters_[key].Value.Equals(value))
                {
                    ParameterChangeArgs args = new ParameterChangeArgs(key, parameters_[key].Value, value);

                    if (null != ParameterBeforeChange)
                        ParameterBeforeChange(this, args);

                    parameters_[args.Key].Value = args.Value;

                    if (null != ParameterAfterChange)
                        ParameterAfterChange(this, args);
                }
            }
        }

        public void Add(String key, Parameter parameter)
        {
            if (!parameters_.ContainsKey(key))
            {
                parameters_.Add(key, parameter);
            }
            else
            {
                parameters_[key] = parameter;
            }
        }

        public void Add(String key)
        {
            Add(key, "");
        }

        public void Add(String key, String value)
        {
            Add(key, value, false);
        }

        public void Add(String key, Boolean encrypt)
        {
            Add(key, "", encrypt);
        }

        public void Add(String key, String value, Boolean encrypt)
        {
            Add(key, value, encrypt, "");
        }

        public void Add(String key, Boolean encrypt, String password)
        {
            Add(key, "", encrypt, password);
        }

        public void Add(String key, String value, Boolean encrypt, String password)
        {
            if (encrypt)
            {
                Add(key, new EncryptedParameter(value, password));
            }
            else
            {
                Add(key, new Parameter(value));
            }
        }

        public string SettingsFilename(bool global, string settings_name)
        {
            if (string.IsNullOrEmpty(settings_name))
            {
                settings_name = this.GetType().Name;
            }

            FileInfo file = new FileInfo(AssemblyHelper.GetExeLocation());
            string filepath = string.Empty;

            if (global)
            {
                string path = AssemblyHelper.GetBasePath();

                filepath = string.Format("{0}\\Settings\\{1}.{2}.xml", path, file.Name.Substring(0, file.Name.Length - file.Extension.Length), settings_name);
            }
            else
            {
                string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

                filepath = string.Format("{0}\\Settings\\{1}.{2}.xml", path, file.FullName.Substring(0, file.FullName.Length - file.Extension.Length).Replace(":", string.Empty), settings_name);
            }

            return filepath;
        }

        public bool SettingsExists(bool global, string settings_name)
        {
            return File.Exists(this.SettingsFilename(global, settings_name));
        }

        public virtual bool SaveSettings(bool global, string settings_name)
        {
            string filename = this.SettingsFilename(global, settings_name);

            if (!File.Exists(filename))
            {
                FileInfo fi = new FileInfo(filename);
                Directory.CreateDirectory(fi.DirectoryName);
            }

            return this.Serialize(this.SettingsFilename(global, settings_name));
        }

        public virtual void LoadSettings(bool global, string settings_name, bool add_missing)
        {
            this.Set(this.Deserialize(this.SettingsFilename(global, settings_name)).ToString(), add_missing);
        }

        public virtual bool ValidateParameter(SettingsControl sc)
        {
            return true;
        }
    }
}
